// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System;
using System.Collections.Generic;

namespace UnityEngine
{
    //Describes a contact point between two Collider objects, used by the Collision class to provide a list of such points when dispatching a OnCollisionX event
    public struct ContactPoint
    {
        internal Vector3 m_Point;
        internal Vector3 m_Normal;
        internal Vector3 m_Impulse;
        internal int m_ThisColliderInstanceID;
        internal int m_OtherColliderInstanceID;
        internal float m_Separation;

        public Vector3 point { get { return m_Point; } }
        public Vector3 normal { get { return m_Normal; } }
        public Vector3 impulse { get { return m_Impulse; } }

        public Collider thisCollider { get { return Physics.GetColliderByInstanceID(m_ThisColliderInstanceID); } }
        public Collider otherCollider { get { return Physics.GetColliderByInstanceID(m_OtherColliderInstanceID); } }
        public float separation { get { return m_Separation; } }

        internal ContactPoint(Vector3 point, Vector3 normal, Vector3 impulse, float separation, int thisInstanceID, int otherInstenceID)
        {
            m_Point = point;
            m_Normal = normal;
            m_Impulse = impulse;
            m_Separation = separation;
            m_ThisColliderInstanceID = thisInstanceID;
            m_OtherColliderInstanceID = otherInstenceID;
        }
    }

    // Describes collision reported via MonoBehaviour.OnCollisionX callbacks.
    public partial class Collision
    {
        private ContactPairHeader m_Header;
        private ContactPair m_Pair;
        private bool m_Flipped;
        private ContactPoint[] m_LegacyContacts = null;

        public Vector3 impulse => m_Pair.impulseSum;
        public Vector3 relativeVelocity => m_Flipped ? m_Header.m_RelativeVelocity : -m_Header.m_RelativeVelocity;
        public Rigidbody rigidbody => body as Rigidbody;
        public ArticulationBody articulationBody => body as ArticulationBody;
        public Component body => m_Flipped ? m_Header.body : m_Header.otherBody;
        public Collider collider => m_Flipped ? m_Pair.collider : m_Pair.otherCollider;
        public Transform transform { get { return rigidbody != null ? rigidbody.transform : collider.transform; } }
        public GameObject gameObject { get { return body != null ? body.gameObject : collider.gameObject; } }
        internal bool Flipped { get { return m_Flipped; } set { m_Flipped = value; } }

        // The number of contacts available.
        public int contactCount { get { return (int)m_Pair.m_NbPoints; } }

        // The contact points generated by the physics engine.
        // NOTE: This produces garbage and should be avoided.
        public ContactPoint[] contacts
        {
            get
            {
                if (m_LegacyContacts == null)
                {
                    m_LegacyContacts = new ContactPoint[m_Pair.m_NbPoints];
                    m_Pair.ExtractContactsArray(m_LegacyContacts, m_Flipped);
                }

                return m_LegacyContacts;
            }
        }

        public Collision()
        {
            m_Header = new ContactPairHeader();
            m_Pair = new ContactPair();
            m_Flipped = false;

            m_LegacyContacts = null;
        }

        // Assumes we are NOT in the reusing mode
        internal Collision(in ContactPairHeader header, in ContactPair pair, bool flipped)
        {
            m_LegacyContacts = new ContactPoint[pair.m_NbPoints];
            pair.ExtractContactsArray(m_LegacyContacts, flipped);
            m_Header = header;
            m_Pair = pair;
            m_Flipped = flipped;
        }

        // Assumes we are in the reusing mode
        internal void Reuse(in ContactPairHeader header, in ContactPair pair)
        {
            m_Header = header;
            m_Pair = pair;
            m_LegacyContacts = null;
            m_Flipped = false;
        }

        // Get contact at specific index.
        public unsafe ContactPoint GetContact(int index)
        {
            if (index < 0 || index >= contactCount)
                throw new ArgumentOutOfRangeException(String.Format("Cannot get contact at index {0}. There are {1} contact(s).", index, contactCount));

            if (m_LegacyContacts != null)
                return m_LegacyContacts[index];

            float sign = m_Flipped ? -1f : 1f;
            var ptr = m_Pair.GetContactPoint_Internal(index);

            return new ContactPoint(
                    ptr->m_Position,
                    ptr->m_Normal * sign,
                    ptr->m_Impulse,
                    ptr->m_Separation,
                    m_Flipped ? m_Pair.otherColliderInstanceID : m_Pair.colliderInstanceID,
                    m_Flipped ? m_Pair.colliderInstanceID : m_Pair.otherColliderInstanceID);
        }

        // Get contacts for this collision.
        public int GetContacts(ContactPoint[] contacts)
        {
            if (contacts == null)
                throw new NullReferenceException("Cannot get contacts as the provided array is NULL.");

            if (m_LegacyContacts != null)
            {
                int length = Mathf.Min(m_LegacyContacts.Length, contacts.Length);
                Array.Copy(m_LegacyContacts, contacts, length);
                return length;
            }

            return m_Pair.ExtractContactsArray(contacts, m_Flipped);
        }

        // Get contacts for this collision.
        public int GetContacts(List<ContactPoint> contacts)
        {
            if (contacts == null)
                throw new NullReferenceException("Cannot get contacts as the provided list is NULL.");

            contacts.Clear();

            if (m_LegacyContacts != null)
            {
                contacts.AddRange(m_LegacyContacts);
                return m_LegacyContacts.Length;
            }

            int n = (int)m_Pair.m_NbPoints;

            if (n == 0)
                return 0;

            if (contacts.Capacity < n) // Resize here instead of in native
                contacts.Capacity = n;

            return m_Pair.ExtractContacts(contacts, m_Flipped);
        }
    }
}
